﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Born2Code.Net;
using System.Text;
using System.Threading;

namespace IOPerformanceVerifier
{
    public class IOController
    {
        public static bool createFile(string absoluteFilePath,int fileSize)
        {
            if (!File.Exists(absoluteFilePath))
            {
                FileStream fs = new FileStream(absoluteFilePath, FileMode.CreateNew);
                fs.Seek(fileSize * 1024L * 1024, SeekOrigin.Begin);
                fs.WriteByte(0);
                fs.Flush();
                fs.Close();
            }

            // verify file
            if (File.Exists(absoluteFilePath))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private ThreadConfigutation _threadConfiguration;
        private ThrottledStream _throttledStream;
        private Logger _logger;
        private byte[] _generatedData;
        private byte[] _readData;
        private int _offset;
        private int _generatedDataSize;
        private long _maxFileSize;
        private Random _random;
        private int _fileParts;
        private int _bufferSize;

        public IOController(ThreadConfigutation threadConfiguration)
        {
            _threadConfiguration = threadConfiguration;
            initialize();
        }

        private void initialize()
        {
            _logger = Logger.Instance();
            _logger.log(LogLevel.Debug, _threadConfiguration.InternalDiskTestName + ": ThrottledStream Initializing");
            generateData();
            createStream();
            _logger.log(LogLevel.Debug, _threadConfiguration.InternalDiskTestName + ": ThrottledStream Created");
        }

        private void generateData()
        {
            _random = new Random();
            _generatedDataSize = 1024 * 1024;
            _generatedData = new byte[_generatedDataSize];
            _readData = new byte[_generatedDataSize];
            _random.NextBytes(_generatedData);
            _bufferSize = _threadConfiguration.BlockSize * 1024;
            _logger.log(LogLevel.Debug, _threadConfiguration.InternalDiskTestName + ": buffer: " + _bufferSize);
            _offset = 0;
            _maxFileSize = _threadConfiguration.InternalFileSize * 1024L * 1024;
            _fileParts = (int) _maxFileSize / _generatedDataSize;
        }

        private void createStream()
        {
            if (_threadConfiguration.ThreadType == IOThreadType.Read)
            { 
                // Create read stream
                FileStream readFileStream = new FileStream(_threadConfiguration.AbsoluteFilePath, FileMode.Open, FileAccess.Read, FileShare.None);
                _throttledStream = new ThrottledStream(readFileStream, (int)Math.Ceiling(_threadConfiguration.ReadPerUnit * _threadConfiguration.PressureMultiplier));
            }
            else if (_threadConfiguration.ThreadType == IOThreadType.Write)
            {
                // Create write stream
                FileStream writeFileStream;
                if(_threadConfiguration.NonBufferedWrite)
                {
                    writeFileStream = new FileStream(_threadConfiguration.AbsoluteFilePath, FileMode.Open, FileAccess.Write);
                }
                else
                {
                    writeFileStream = new FileStream(_threadConfiguration.AbsoluteFilePath, FileMode.Open, FileAccess.Write,FileShare.None, _bufferSize);
                }
                _throttledStream = new ThrottledStream(writeFileStream, (int)Math.Ceiling(_threadConfiguration.WritePerUnit * _threadConfiguration.PressureMultiplier));
            }
            else
            {
                throw new Exception("Thread type is not set to understandable value. Stream could not be created");
            }
        }

        public void runStep()
        {
            _logger.log(LogLevel.Debug, _threadConfiguration.InternalDiskTestName + ": runStep started");
            // TODO Take time

            try
            {
                if (_threadConfiguration.ThreadType == IOThreadType.Write && _throttledStream.CanWrite)
                {
                    _throttledStream.Seek(_offset, SeekOrigin.Begin);
                    _throttledStream.Write(_generatedData, 0, _generatedDataSize);
                    _offset = generateNewOffset();
                }
                else if (_threadConfiguration.ThreadType == IOThreadType.Read && _throttledStream.CanRead)
                {
                    _throttledStream.Seek(_offset, SeekOrigin.Begin);
                    _throttledStream.Read(_generatedData, 0, _generatedDataSize);
                    _readData = _generatedData;
                    _offset = generateNewOffset();
                }
            }
            catch (Exception e)
            {
                _logger.log(LogLevel.Error, e);
            }
            // TODO Take time
            // TODO sleep for the rest of the time
            //Thread.Sleep(1000);
            _logger.log(LogLevel.Debug, _threadConfiguration.InternalDiskTestName + ": runStep finished");
        }

        private int generateNewOffset()
        {
            if (_threadConfiguration.IsSequentialIOSet)
            {
                if ((_offset + (_generatedDataSize * 2)) > _maxFileSize)
                {
                    return 0;
                }
                else
                {
                    return _offset + _generatedDataSize;
                }
            }
            else
            {
                return generateNewRandomOffset();
            }
        }

        private int generateNewRandomOffset()
        {
            return _random.Next(_fileParts);
        }

        public void cleanUp()
        {
            if (_threadConfiguration.ThreadType != IOThreadType.NotSet && _throttledStream != null)
            {
                _throttledStream.Dispose();
                _logger.log(LogLevel.Debug, "Stream disposed");
            }
            else if (_throttledStream == null)
            {
                _logger.log(LogLevel.Debug, "No stream to dispose");
            }
            else
            {
                throw new Exception("Thread type is not set to understandable value.");
            }
        }
    }
}
